package com.tj.bo.trade.server.mq;

import com.alibaba.fastjson.JSONObject;
import com.fqgj.base.services.redis.RedisClient;
import com.fqgj.common.utils.CollectionUtils;
import com.fqgj.common.utils.DateUtil;
import com.fqgj.common.utils.StringUtils;
import com.fqgj.exception.common.ApplicationException;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.tj.bo.account.client.AccountBalanceChangeService;
import com.tj.bo.account.client.DepositService;
import com.tj.bo.account.client.enums.IncomeEnum;
import com.tj.bo.account.client.vo.AccountIncomeVO;
import com.tj.bo.common.conf.config.RedisConfigUtil;
import com.tj.bo.common.conf.constant.RedisKey;
import com.tj.bo.common.conf.enums.BonusStatusEnum;
import com.tj.bo.common.conf.enums.ProductEnum;
import com.tj.bo.common.conf.enums.UserTypeEnum;
import com.tj.bo.common.conf.mq.RocketMQConsumerHelper;
import com.tj.bo.common.conf.mq.RocketMQTag;
import com.tj.bo.common.conf.socket.SocketBodyInfo;
import com.tj.bo.common.conf.socket.SocketMessageInfo;
import com.tj.bo.common.conf.socket.SocketMessageUtil;
import com.tj.bo.common.conf.util.CalculatorUtils;
import com.tj.bo.common.conf.util.DecimalUtils;
import com.tj.bo.common.conf.util.SearchUtils;
import com.tj.bo.common.conf.vo.MarketTimePrice;
import com.tj.bo.product.client.ProductService;
import com.tj.bo.product.client.vo.Product;
import com.tj.bo.trade.client.SettlementOrders;
import com.tj.bo.trade.client.enums.OrderStatusEnum;
import com.tj.bo.trade.client.enums.ProfitReminderEnum;
import com.tj.bo.trade.client.enums.ProfitStatusEnum;
import com.tj.bo.trade.client.enums.TradeTypeEnum;
import com.tj.bo.trade.client.response.GuideRealTradeSocketBody;
import com.tj.bo.trade.server.dao.BinaryTradeDao;
import com.tj.bo.trade.server.entity.BinaryTradeEntity;
import com.tj.bo.trade.server.ro.ProfitIntervalValueRO;
import com.tj.bo.trade.server.service.TradeHandler;
import com.tj.bo.user.client.UserPromotionService;
import com.tj.bo.user.client.UserQueryService;
import com.tj.bo.user.client.UserWanningListService;
import com.tj.bo.user.client.vo.output.UserBriefInfoRO;
import com.tj.bo.user.client.vo.output.UserPromotionInviteRecordRO;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @program: binary-option
 * @description 结算交易
 * @author: binary
 * @create: 2020-06-16 16:35
 **/
//@RocketMQTag("settlement_orders")
@Service
public class SettlementOrdersConsumer implements SettlementOrders {

    private static final Logger LOGGER = LoggerFactory.getLogger(SettlementOrdersConsumer.class);

    @Autowired
    private TradeHandler tradeHandler;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private UserQueryService userQueryService;
    @Autowired
    private ProductService productService;
    @Autowired
    private BinaryTradeDao binaryTradeDao;
    @Autowired
    private RedisConfigUtil redisDbConfigUtil;
    @Autowired
    private UserPromotionService userPromotionService;
    @Autowired
    private AccountBalanceChangeService accountBalanceChangeService;
    @Autowired
    private RedisClient redisClient;
    @Autowired
    private UserWanningListService userWanningListService;
    @Autowired
    private DepositService depositService;

    public void onMessage(String tradeNo) {
        LOGGER.info("SettlementOrdersConsumer,接收交易mq消息,tradeNo:{}", tradeNo);
        if (StringUtils.isEmpty(tradeNo)) {
            return;
        }
        BinaryTradeEntity entity = binaryTradeDao.selectBinaryTradeByTradeNo(tradeNo);
        if (entity == null) {
            LOGGER.info("SettlementOrdersConsumer,entity为空,tradeNo:{}", tradeNo);
            return;
        }
        Product product = productService.getProductByCode(entity.getProductCode());

        String mockPrice = redisClient.get(RedisKey.BINARY_OPTION_SETTLEMENT_KEY, entity.getTradeNo());
        BigDecimal closingPrice = null;
        if (mockPrice == null) {
            closingPrice = getClosingPrice(entity.getProductCode(), entity.getTheoryExpectDeadline().getTime());
        } else {
            LOGGER.info("SettlementOrdersConsumer,采用mock结算价,tradeNo:{},price:{}", entity.getTradeNo(), mockPrice);
            closingPrice = new BigDecimal(mockPrice);
        }
        if (closingPrice.compareTo(BigDecimal.ZERO) == 0) {
            throw new ApplicationException("settlement fail. Amount is 0.");
        }

        entity.setClosingPrice(closingPrice);

        BigDecimal profitAmount = CalculatorUtils.calculateProfit(entity.getOpeningPrice(), entity.getClosingPrice(), entity.getAmount(), entity.getTrendType(), entity.getSettlementProfit());
        boolean result = tradeHandler.settlementOrders(entity, product, profitAmount, closingPrice);
        //发送socket
        if (result) {
            this.addGreyUser(entity.getUserCode(), entity.getTradeNo(), entity.getTradeType());
            //写入odd处理队列 单线程job处理可以避免并发问题
            redisClient.listLeftPush(RedisKey.BINARY_OPTION_ODDS_CALC_LIST, entity.getUserCode() + "," + entity.getProductCode());
            this.pushSocket(entity, profitAmount);
            this.inviteBonusHandler(entity.getUserCode(), tradeNo);
        }
        LOGGER.info("SettlementOrdersConsumer,处理交易mq消息成功,tradeNo:{}", tradeNo);
    }

    /**
     * 触发添加灰名单
     * @param userCode
     */
    private void addGreyUser(String userCode, String tradeNo, Integer tradeType){
        if(tradeType == TradeTypeEnum.REAL_TRADE.getCode()){
            List<ProfitIntervalValueRO> profitIntervalValues = JSONObject.parseArray(redisDbConfigUtil.getProfitIntervalValue(), ProfitIntervalValueRO.class);
            BigDecimal totalProfitAmount = binaryTradeDao.calculateUserTotalProfitAmount(null, userCode);
            BigDecimal totalDepositAmount = depositService.selectTotalDepositAmount(userCode);
            if(totalDepositAmount.compareTo(BigDecimal.ZERO) == 0 || totalProfitAmount.compareTo(BigDecimal.ZERO) <= 0){
                return;
            }
            BigDecimal multiple = totalProfitAmount.divide(totalDepositAmount).setScale(2, BigDecimal.ROUND_HALF_UP);
            boolean isGrey =false;
            for(ProfitIntervalValueRO profitIntervalValueRO : profitIntervalValues){
                if(totalDepositAmount.compareTo(new BigDecimal(profitIntervalValueRO.getLower())) >= 0
                        && totalDepositAmount.compareTo(new BigDecimal(profitIntervalValueRO.getUpper())) < 0
                        && multiple.compareTo(new BigDecimal(profitIntervalValueRO.getMultiple()))>=0){
                    isGrey = true;
                    break;
                }
            }
            LOGGER.info("判断是否灰名单,isGrey:{}, userCode:{}, tradeNo:{}, totalProfitAmount:{}, totalDepositAmount:{}, multiple:{}", isGrey, userCode, tradeNo, totalProfitAmount, totalDepositAmount, multiple);
            if(isGrey){
                userWanningListService.addUser(userCode, UserTypeEnum.GREY.getCode());
            }
        }
    }

    /**
     * 邀请好友赠送奖励金
     *
     * @param userCode
     */
    private void inviteBonusHandler(String userCode, String tradeNo) {
        RLock lock = redissonClient.getLock(String.format(RedisKey.BINARY_OPTION_INVITE_FRIEND_BONUS_KEY, userCode));
        lock.lock(5, TimeUnit.SECONDS);
        try {
            UserPromotionInviteRecordRO userPromotionInviteRecordRO = userPromotionService.getUserPromotionInviteRecordByInvitedUserCode(userCode);
            if (userPromotionInviteRecordRO != null
                    && userPromotionInviteRecordRO.getStatus() == BonusStatusEnum.PROCESSING.getCode()) {
                userPromotionService.updateUserPromotionBonusAndNumber(userPromotionInviteRecordRO.getUserCode(), userPromotionInviteRecordRO.getInvitedUserCode(), redisDbConfigUtil.getInviteBonus());
                //增加奖励金
                this.addBalance(new BigDecimal(redisDbConfigUtil.getInviteBonus()), userPromotionInviteRecordRO.getUserCode(), tradeNo);
            }
        } catch (Exception e) {
            LOGGER.error("============= 邀请好友增加赠送奖金异常 e: ===========", e);
        } finally {
            lock.unlock();
        }
    }

    /**
     * 增加奖励金
     *
     * @param bonus
     * @param userCode
     * @param tradeNo
     */
    private void addBalance(BigDecimal bonus, String userCode, String tradeNo) {
        if (bonus.compareTo(BigDecimal.ZERO) > 0) {
            AccountIncomeVO accountIncome = new AccountIncomeVO();
            accountIncome.setUserCode(userCode);
            accountIncome.setOrderNo(tradeNo);
            accountIncome.setAmount(bonus);
            accountIncome.setGrants(BigDecimal.ZERO);
            accountIncome.setIncomeEnum(IncomeEnum.BONUS);
            accountBalanceChangeService.income(accountIncome);
        }
    }

    /**
     * 获取结算价格
     *
     * @param productCode
     * @param openingTimestamp
     * @return
     */
    private BigDecimal getClosingPrice(String productCode, Long openingTimestamp) {
        BigDecimal openingPrice = BigDecimal.ZERO;
        long timeStart = openingTimestamp - 8000;
        Set<ZSetOperations.TypedTuple<Object>> openingPriceSet = redisClient.zRangeByScoreWithScores(RedisKey.ZSET_BINARY_OPTION_PRODUCT_KEY, timeStart, openingTimestamp, productCode);
        if (CollectionUtils.isEmpty(openingPriceSet)) {
            LOGGER.warn("============= market data in redis is empty, pls check ============,productCode=" + productCode + ",openingTimestamp=" + openingTimestamp);
            return openingPrice;
        }
        List<Object> openingPriceStrList = this.getList(Lists.newArrayList(openingPriceSet));
        List<Long> timestampList = Lists.newArrayList();
        Map<Long, String> pricesMap = Maps.newHashMap();
        openingPriceStrList.stream().forEach(marketData -> {
            MarketTimePrice redisMarketTimePrice = JSONObject.parseObject((String) marketData, MarketTimePrice.class);
            timestampList.add(redisMarketTimePrice.getMarketTimestamp());
            pricesMap.put(redisMarketTimePrice.getMarketTimestamp(), redisMarketTimePrice.getMarketPrice());
        });

        Long redisOpeningTimestamp = SearchUtils.binarySearchSettlementTime(timestampList, openingTimestamp);
        openingPrice = new BigDecimal(pricesMap.get(redisOpeningTimestamp));
        LOGGER.info("=================== settlement order get price:{}, openingTimestamp:{}, redisOpeningTimestamp:{} ==================", openingPrice, openingTimestamp, redisOpeningTimestamp);
        return openingPrice;
    }

    private List<Object> getList(List<ZSetOperations.TypedTuple<Object>> priceList) {
        List<Object> newList = Lists.newArrayList();
        for (int i = 0; i < priceList.size(); i++) {
            ZSetOperations.TypedTuple<Object> typedTuple = priceList.get(i);
            newList.add(typedTuple.getValue());
        }
        return newList;
    }

    private void pushSocket(BinaryTradeEntity entity, BigDecimal profitAmount) {

        List<BinaryTradeEntity> binaryTradeEntities = binaryTradeDao.selectBinaryTradeListByUserCodeAndProductCode(entity.getUserCode(), null, OrderStatusEnum.CLOSING.getCode(), null);
        List<BinaryTradeEntity> realBinaryTradeEntities = Lists.newArrayList();
        List<BinaryTradeEntity> virtualBinaryTradeEntities = Lists.newArrayList();

        if (CollectionUtils.isNotEmpty(binaryTradeEntities)) {
            for (BinaryTradeEntity binaryTradeEntity : binaryTradeEntities) {
                if (binaryTradeEntity.getTradeType() == TradeTypeEnum.REAL_TRADE.getCode()) {
                    realBinaryTradeEntities.add(binaryTradeEntity);
                } else {
                    virtualBinaryTradeEntities.add(binaryTradeEntity);
                }
            }
        }

        //发送结算socket
        SocketBodyInfo socketBodyInfo = new SocketBodyInfo();
        socketBodyInfo.setProductName(ProductEnum.getEnumByProductCode(entity.getProductCode()).getName());
        socketBodyInfo.setProductIcon(ProductEnum.getEnumByProductCode(entity.getProductCode()).getLogo());
        socketBodyInfo.setTrendType(entity.getTrendType());
        socketBodyInfo.setAmount(entity.getAmount().toString());
        socketBodyInfo.setProfitPercent(entity.getProfit().multiply(new BigDecimal(100)).intValue() + "%");
        socketBodyInfo.setTradeNo(entity.getTradeNo());
        socketBodyInfo.setProfitAmount("PKR " + DecimalUtils.formatDecimal(profitAmount));
        socketBodyInfo.setProductCode(entity.getProductCode());
        socketBodyInfo.setOpeningPrice(entity.getOpeningPrice().stripTrailingZeros().toPlainString());
        socketBodyInfo.setClosingPrice(entity.getClosingPrice().stripTrailingZeros().toPlainString());
        socketBodyInfo.setProfitStatus(entity.getProfitStatus());
        socketBodyInfo.setOpeningTime(DateUtil.getDate(entity.getOpeningTime(), "HH:mm:ss"));
        socketBodyInfo.setClosingTime(DateUtil.getDate(entity.getTheoryExpectDeadline(), "HH:mm:ss"));
        socketBodyInfo.setTips(this.consecutiveVictories(realBinaryTradeEntities, virtualBinaryTradeEntities, entity.getTradeType(), entity.getProfitStatus()));

        UserBriefInfoRO userBriefInfoRO = userQueryService.getUserInfoByUserCode(entity.getUserCode());
        SocketMessageInfo socketMessageInfo = new SocketMessageInfo();
        socketMessageInfo.setBizCode("S0002");
        socketMessageInfo.setBody(JSONObject.toJSONString(socketBodyInfo));
        socketMessageInfo.setToken(userBriefInfoRO.getRealToken());
        socketMessageInfo.setDeviceId(userBriefInfoRO.getDeviceId());
        SocketMessageUtil.pushSocketMessage(socketMessageInfo, redisDbConfigUtil.getWebSocketHostNew() + "/message/single/push");
        LOGGER.info("============ 发送socket message成功，tradeNo:{}, profitAmount:{},deviceId:{} ==========", entity.getTradeNo(), profitAmount, userBriefInfoRO.getDeviceId());

        //推送引导注册socket
        this.pushGuideRealDealsMsg(StringUtils.isEmpty(userBriefInfoRO.getRealToken()), entity.getProfitStatus(), entity.getUserCode(), userBriefInfoRO.getDeviceId(), virtualBinaryTradeEntities);
    }

    /**
     * 连续盈利提示
     *
     * @param realBinaryTradeEntities
     * @param virtualBinaryTradeEntities
     * @param tradeType
     * @return
     */
    private String consecutiveVictories(List<BinaryTradeEntity> realBinaryTradeEntities, List<BinaryTradeEntity> virtualBinaryTradeEntities, Integer tradeType, Integer profitStatusCur) {
        if (profitStatusCur == ProfitStatusEnum.UN_PROFIT.getCode()) {
            return "Your prediction was incorrect. \nBetter luck next time.";
        }
        List<Integer> profitStatusList = Lists.newArrayList();
        if (tradeType == TradeTypeEnum.VIRTUAL_TRADE.getCode()) {
            for (BinaryTradeEntity entity : virtualBinaryTradeEntities) {
                profitStatusList.add(entity.getProfitStatus());
            }
        } else {
            for (BinaryTradeEntity entity : realBinaryTradeEntities) {
                profitStatusList.add(entity.getProfitStatus());
            }
        }
        Collections.reverse(profitStatusList);
        List<Integer> positiveProfitStatusList = Lists.newArrayList();
        for (Integer profitStatus : profitStatusList) {
            if (profitStatus == ProfitStatusEnum.UN_PROFIT.getCode()) {
                break;
            }
            if (profitStatus == ProfitStatusEnum.PROFIT.getCode()) {
                positiveProfitStatusList.add(profitStatus);
            }
        }
        return ProfitReminderEnum.getEnumByCode(positiveProfitStatusList.size()).getName() + ", You Won!";
    }

    private void pushGuideRealDealsMsg(boolean isVisitor, Integer profitStatus, String userCode, String deviceId, List<BinaryTradeEntity> binaryTradeEntities) {
        if (isVisitor) {
            RLock lock = redissonClient.getLock(String.format(RedisKey.BINARY_OPTION_REAL_TRADE_PUSH, userCode));
            lock.lock(10, TimeUnit.SECONDS);
            try {
                if (CollectionUtils.isNotEmpty(binaryTradeEntities)) {
                    Integer visitorTradeNumbers = 0;
                    for (BinaryTradeEntity entity : binaryTradeEntities) {
                        if (entity.getProfitStatus() == ProfitStatusEnum.PROFIT.getCode()) {
                            visitorTradeNumbers = visitorTradeNumbers + 1;
                        }
                    }

                    String guideRealTradeNumbers = redisDbConfigUtil.guideRealTradeNumbers();
                    boolean divisible = false;
                    for (String number : guideRealTradeNumbers.split(",")) {
                        if (visitorTradeNumbers % Integer.valueOf(number) == 0) {
                            divisible = true;
                            break;
                        }
                    }
                    if (visitorTradeNumbers != 0 && divisible && profitStatus == ProfitStatusEnum.PROFIT.getCode()) {
                        GuideRealTradeSocketBody body = new GuideRealTradeSocketBody();
                        body.setTouristProfitableNumbers(visitorTradeNumbers.toString());
                        SocketMessageInfo socketMessageInfo = new SocketMessageInfo();
                        socketMessageInfo.setBizCode("S0003");
                        socketMessageInfo.setBody(JSONObject.toJSONString(body));
                        socketMessageInfo.setToken("");
                        socketMessageInfo.setDeviceId(deviceId);
                        SocketMessageUtil.pushSocketMessage(socketMessageInfo, redisDbConfigUtil.getWebSocketHostNew() + "/message/single/push");
                        LOGGER.info("============== 游客模式下推送引导用户注册msg, userCode:{}, deviceId:{}, 订单数:{}", userCode, deviceId, visitorTradeNumbers);
                    }
                }
            } catch (Exception e) {
                LOGGER.error("======= 游客模式下推送引导用户注册msg推送失败，e:" + e);
            } finally {
                lock.unlock();
            }
        }
    }

    @Override
    public void solve(String tradeNo) {
        onMessage(tradeNo);
    }
}
